package com.mjf.thread;

/**
 * <p>线程安全问题：
 *  当多个线程同时共享同一个全局变量或静态变量，做写的操作时，可能会发生数据冲突问题，也就是线程安全问题。
 *  但是做读操作是不会发生数据冲突问题。
 *
 * <p>线程安全的解决方式：
 *  使用多线程之间 同步 或 使用锁(lock) 可以解决线程安全问题。
 *  其核心在于将可能会发生数据冲突问题(线程不安全问题)，只能让当前一个线程进行执行。
 *  代码执行完成后释放锁，然后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
 *
 * <p>线程同步：
 *  多个线程共享同一个资源的环境下,每个线程工作时不会受到其他线程的干扰称之为多线程之间的同步。
 *
 * <p>线程同步方法：要求多个线程的锁对象是同一对象，否在无法达到同步效果
 *  1.使用同步代码块(注意：如果使用Thread继承的方式实现多线程，那么同步代码块需要传入同一个对象！)
 *  2.使用同步方法(注意：如果使用Thread继承的方式实现多线程，那么同步方法需要是一个静态的方法！)
 */
public class ThreadSync {

    public static void main(String[] args) {

        // 创建AA线程
        Ticket aa = new Ticket();
        aa.setName("AA");
        aa.start();

        // 创建BB线程
        Ticket bb = new Ticket();
        bb.setName("BB");
        bb.start();

        // 创建CC线程
        Ticket cc = new Ticket();
        cc.setName("CC");
        cc.start();

    }
}


class Ticket extends Thread {
    private static int number = 100;
    private Boolean loop = true;

    @Override
    public void run() {
        while (loop) {
//            sale(); // 会超卖
//            syncSale(); // 会超卖
//            staticSyncSale(); // 正常售卖

            // 使用 this 在不同对象调用时无法达到同步效果
/*            synchronized (this) {
                sale(); // 会超卖
            }*/

            synchronized (Ticket.class) {
                sale(); // 正常售卖
            }

            if(number <= 0) {
                loop = false;
            }
        }
    }

    // 静态同步方法：静态同步方法的默认锁是当前类.class
    public static synchronized void staticSyncSale() {
        if (number > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "卖出第：\t" + (100 - number) + "\t 还剩下：" + number);
        }
    }

    // 非静态同步方法：非静态同步方法的默认锁是this（对于不用的对象无法达到同步的效果）
    public synchronized void syncSale() {
        if (number > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "卖出第：\t" + (100 - number) + "\t 还剩下：" + number);
        }
    }

    // 普通方法
    public void sale() {
        if (number > 0) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            number--;
            System.out.println(Thread.currentThread().getName() + "卖出第：\t" + (100 - number) + "\t 还剩下：" + number);
        }
    }

}
